
#include "Application.h"

#include <iostream>

namespace ErkangShuttle
{
	PFN_vkGetInstanceProcAddr g_vkGetInstanceProcAddr;
	Application& Application::m_Application = *new Application;
	
	Application& Application::getApplicationInstance()
	{
		return m_Application;
	}
	Application::~Application()
	{
		if (m_VulkanLibrary != nullptr)
		{
			FreeDynamicLibrary();
		}
	}
	void Application::init()
	{
		if (!IsLibraryLoaded())
		{
			if (!LoadDynamicLibrary())
			{
				throw(std::string("Load Vulkan Dynamic Library Failure."));
			}
			if (!LoadExportedFunctions())
			{
				throw(std::string("Load Vulkan Exported Functions Failure."));
			}
			if (!LoadGlobalFunctions())
			{
				throw(std::string("Load Vulkan Global Functions Failure."));
			}
			if (!GetAllInstanceExtensions())
			{
				throw(std::string("Get All Vulkan Instance Extensions Failure."));
			}
		}
	}

	void Application::setApplicationInfo(const char* _ApplicationName, uint32_t _ApplicationVersion, const char* _EngineName, uint32_t _EngineVersion)
	{
		m_VkApplicationInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
		m_VkApplicationInfo.pNext = nullptr;
		m_VkApplicationInfo.pApplicationName = _ApplicationName;
		m_VkApplicationInfo.applicationVersion = _ApplicationVersion;
		m_VkApplicationInfo.pEngineName = _EngineName;
		m_VkApplicationInfo.engineVersion = _EngineVersion;
		m_VkApplicationInfo.apiVersion = VK_MAKE_VERSION(1, 0, 68);
	}

	Instance& Application::createInstance(const std::string& _InstanceName, const std::vector<const char*>& _InstanceExtensions)
	{
		InstanceStructApplication* pInstanceStructApplication = new InstanceStructApplication;
		pInstanceStructApplication->_InstanceStruct._VkInstanceCreateInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
		pInstanceStructApplication->_InstanceStruct._VkInstanceCreateInfo.pNext = nullptr;
		pInstanceStructApplication->_InstanceStruct._VkInstanceCreateInfo.flags = 0;
		pInstanceStructApplication->_InstanceStruct._VkInstanceCreateInfo.pApplicationInfo = &m_VkApplicationInfo;
		pInstanceStructApplication->_InstanceStruct._VkInstanceCreateInfo.enabledLayerCount = 0;
		pInstanceStructApplication->_InstanceStruct._VkInstanceCreateInfo.ppEnabledLayerNames = nullptr;
		if (_InstanceExtensions.size() == 0 && m_DefaultInstanceExtensionNames.size() != 0)
		{
			pInstanceStructApplication->_InstanceStruct._VkInstanceCreateInfo.enabledExtensionCount = (uint32_t)m_DefaultInstanceExtensionNames.size();
			pInstanceStructApplication->_InstanceStruct._VkInstanceCreateInfo.ppEnabledExtensionNames = m_DefaultInstanceExtensionNames.data();
		}
		else
		{
			pInstanceStructApplication->_InstanceStruct._VkInstanceCreateInfo.enabledExtensionCount = (uint32_t)_InstanceExtensions.size();
			pInstanceStructApplication->_InstanceStruct._VkInstanceCreateInfo.ppEnabledExtensionNames = _InstanceExtensions.data();
		}
		VkResult result = m_ListOfGlobalFunctions.vkCreateInstance(&pInstanceStructApplication->_InstanceStruct._VkInstanceCreateInfo, nullptr, &pInstanceStructApplication->_InstanceStruct._VkInstance);
		pInstanceStructApplication->_InstanceStruct._pListOfInstanceFunctions = LoadInstanceFunctions(&pInstanceStructApplication->_InstanceStruct._VkInstance);
		Instance* pInstance = new Instance(&pInstanceStructApplication->_InstanceStruct);
		pInstance->init();
		pInstanceStructApplication->_pInstance = pInstance;
		if ((result != VK_SUCCESS) || (pInstanceStructApplication->_InstanceStruct._VkInstance == VK_NULL_HANDLE))
		{
			throw(std::string("Create Vuklan Instance Failure."));
		}
		m_InstanceStructApplication.emplace(_InstanceName, pInstanceStructApplication);

		return *pInstance;
	}

	void Application::destroyInstance(const std::string& _InstanceName)
	{
		for (auto iter = m_InstanceStructApplication.begin(); iter != m_InstanceStructApplication.end(); ++iter)
		{
			if (iter->first == _InstanceName && &iter->second != nullptr)
			{
				delete iter->second->_pInstance;
				delete iter->second->_InstanceStruct._pListOfInstanceFunctions;
				delete iter->second;
				m_InstanceStructApplication.erase(iter);
				return;
			}
		}
		throw(_InstanceName + std::string(" Instance Can Not Be Found."));
	}

	std::vector<const char*>& Application::getAllInstanceExtensions()
	{
		return m_AllAvailableVkInstanceExtensionNames;
	}

	void Application::setDefaultInstanceExtensions(std::vector<const char*>& _InstanceExtensions)
	{
		if (_InstanceExtensions.size() != 0)
		{
			if (IsInstanceExtensionsAvailable(_InstanceExtensions))
			{
				m_DefaultInstanceExtensionNames = _InstanceExtensions;
			}
			else
			{
				throw(std::string("Set Default Instance Extensions Failure."));
			}
		}
	}

	Instance& Application::getInstance(const std::string& _InstanceName)
	{
		auto instance = m_InstanceStructApplication.find(_InstanceName);
		if (instance != m_InstanceStructApplication.end())
		{
			return *instance->second->_pInstance;
		}
		else
		{
			throw(_InstanceName + std::string(" Instance Can Not Be Found."));
		}
	}

	Instance& Application::operator[](const std::string& _InstanceName)
	{
		auto instance = m_InstanceStructApplication.find(_InstanceName);
		if (instance != m_InstanceStructApplication.end())
		{
			return *instance->second->_pInstance;
		}
		else
		{
			return createInstance(_InstanceName);
		}
	}

	bool Application::LoadDynamicLibrary()
	{
#if defined _WIN32
		m_VulkanLibrary = LoadLibrary("vulkan-1.dll");
#elif defined __linux
		m_VulkanLibrary = dlopen("libvulkan.so.1", RTLD_NOW);
#endif
		if (m_VulkanLibrary == nullptr)
		{
			return false;
		}
		return true;
	}

	bool Application::FreeDynamicLibrary()
	{
#if defined _WIN32 
		FreeLibrary(m_VulkanLibrary);
#elif defined __linux 
		dlclose(m_VulkanLibrary);
#endif
		m_VulkanLibrary = nullptr;
		return true;
	}

	bool Application::IsLibraryLoaded()
	{
		if (m_VulkanLibrary == nullptr)
		{
			return false;
		}
		return true;
	}

	bool Application::LoadExportedFunctions()
	{
		g_vkGetInstanceProcAddr = (PFN_vkGetInstanceProcAddr)LoadFunction(m_VulkanLibrary, "vkGetInstanceProcAddr");
		if (g_vkGetInstanceProcAddr == nullptr)
		{
			return false;
		}
		return true;
	}

	bool Application::LoadGlobalFunctions()
	{
#define globalfunction(name)																	\
		m_ListOfGlobalFunctions.##name = (PFN_##name)g_vkGetInstanceProcAddr(nullptr, #name);	\
		if(m_ListOfGlobalFunctions.##name == nullptr)											\
		{																						\
			return false;																		\
		}
		globalfunction(vkEnumerateInstanceExtensionProperties);
		globalfunction(vkEnumerateInstanceLayerProperties);
		globalfunction(vkCreateInstance);
		return true;
	}

	bool Application::GetAllInstanceExtensions()
	{
		uint32_t InstanceExtensionsCount = 0;
		VkResult ECResult = m_ListOfGlobalFunctions.vkEnumerateInstanceExtensionProperties(nullptr, &InstanceExtensionsCount, nullptr);
		m_AllAvailableVkInstanceExtensions.resize(InstanceExtensionsCount);
		VkResult EPResult = m_ListOfGlobalFunctions.vkEnumerateInstanceExtensionProperties(nullptr, &InstanceExtensionsCount, m_AllAvailableVkInstanceExtensions.data());
		if (ECResult != VK_SUCCESS || EPResult != VK_SUCCESS || InstanceExtensionsCount == 0)
		{
			return false;
		}
		for (auto& InstanceExtension : m_AllAvailableVkInstanceExtensions)
		{
			m_AllAvailableVkInstanceExtensionNames.push_back(InstanceExtension.extensionName);
		}
		return true;
	}

	bool Application::IsInstanceExtensionsAvailable(std::vector<const char*>& _InstanceExtensions)
	{
		uint32_t i = 0;
		for (auto& InstanceExtension : _InstanceExtensions)
		{
			for (auto& AvailableInstanceExtensionname : m_AllAvailableVkInstanceExtensionNames)
			{
				if (std::string(AvailableInstanceExtensionname) == std::string(InstanceExtension))
				{
					i++;
					break;
				}
			}
		}
		if (i == _InstanceExtensions.size())
		{
			return true;
		}
		return false;
	}

	ListOfInstanceFunctions* Application::LoadInstanceFunctions(VkInstance* _pVkInstance)
	{
		ListOfInstanceFunctions* pListOfInstanceFunctions = new ListOfInstanceFunctions;
#define instancefunction(name)																			\
		pListOfInstanceFunctions->##name = (PFN_##name)g_vkGetInstanceProcAddr(*_pVkInstance, #name);	\
		if(pListOfInstanceFunctions->##name == nullptr)													\
		{																								\
			return nullptr;																				\
		}
		instancefunction(vkEnumeratePhysicalDevices);
		instancefunction(vkDestroyInstance);
		return pListOfInstanceFunctions;
	}

}
